package danran.dbapi.servlet;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import danran.dbapi.common.ApiConfig;
import danran.dbapi.common.ApiSql;
import danran.dbapi.common.ResponseDto;
import danran.dbapi.core.SqlMeta;
import danran.dbapi.domain.DataSource;
import danran.dbapi.domain.Token;
import danran.dbapi.plugin.CachePlugin;
import danran.dbapi.plugin.PluginManager;
import danran.dbapi.service.*;
import danran.dbapi.utils.JDBCUtil;
import danran.dbapi.utils.Privilege;
import danran.dbapi.utils.SQLEngineUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

/**
 * @Classname APIServlet
 * @Description TODO
 * @Date 2022/1/14 9:42
 * @Created by RanCoder
 */
@Component
public class APIServlet extends HttpServlet {

    private static Logger log = LoggerFactory.getLogger(APIServlet.class);

    @Autowired
    private ApiConfigService apiConfigService;

    @Autowired
    private DataSourceService dataSourceService;

    @Autowired
    private ApiService apiService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private IPService ipService;


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("====servlet execute====");
        String requestURI = req.getRequestURI();
        String servletPath = requestURI.substring(5);
        log.info("Request URI : " + requestURI);
        log.info("Servlet Path : " + servletPath);

        PrintWriter out = null;
        try {
            out = resp.getWriter();
            ResponseDto responseDto = process(servletPath, req, resp);
            log.info("Append response : " + JSON.toJSONString(responseDto));
            out.append(JSON.toJSONString(responseDto));
            log.info("====Servlet execute done====");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }

    /***
     * 数据接口处理
     * @param path 请求路径
     * @param req
     * @param resp
     * @return 响应
     */
    private ResponseDto process(String path, HttpServletRequest req, HttpServletResponse resp) {
        try {
            // 校验接口是否存在
            ApiConfig config = apiConfigService.getConfig(path);
            if (config == null) {
                resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return ResponseDto.fail("Api not exists!");
            }
            // 如果是私有接口，需要校验权限
            if (config.getPrevilege() == Privilege.PRIVATE) {
                String tokenStr = req.getHeader("Authorization");
                log.info("[Authorization token str] : " + tokenStr);
                if (tokenStr == null || "".equals(tokenStr)) {
                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return ResponseDto.fail("No token!");
                }
                // 得到token， 验证token有效性
                Token token = tokenService.getToken(tokenStr);
                if (token == null) {// token无效
                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return ResponseDto.fail("Invalid token!");
                }
                // 检查token是否过期
                if (token.getExpire() != null && token.getExpire() < System.currentTimeMillis()) {
                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return ResponseDto.fail("Token overdue!");
                }
                // token有效
                // 得到该token授予的api组（Group）
                List<String> authGroups = tokenService.getAuthGroups(token.getId());
                if (!checkAuth(authGroups, config.getGroupId())) {
                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return ResponseDto.fail("This token has no privilege for this api!");
                }
            }

            // 验证数据源
            DataSource dataSource = dataSourceService.detail(config.getDatasourceId());
            if (dataSource == null) {
                resp.setStatus(500);
                return ResponseDto.fail("No datasource available!");
            }

            // 获取sql参数
            Map<String, Object> sqlParam = apiService.getSqlParam(req, config);

            // 从缓存中获取数据
            String cache_plugin = config.getCachePlugin();// 获得缓存插件的全类名字符串
            log.info("Cache Plugin :{}", cache_plugin == null ? "null" : cache_plugin);
            if (cache_plugin != null && !"".equals(config.getCachePlugin())) {
                CachePlugin cachePlugin = PluginManager.getCachePlugin(config.getCachePlugin());
                Object data = cachePlugin.get(config, sqlParam);
                if (data != null) {
                    // 返回缓存中的数据，执行结束
                    return ResponseDto.apiSuccess(data);
                }
            }

            // 缓存中没有对应的数据，需要进行数据库查询
            List<ApiSql> sqlList = config.getSqlList();
            List<Object> result = new ArrayList<>();
            for (ApiSql apiSql : sqlList) {
                // 获得Sql元数据
                SqlMeta sqlMeta =
                        SQLEngineUtil.getEngine().parse(apiSql.getSqlText(), sqlParam);
                Object data = JDBCUtil.executeSql(dataSource, sqlMeta.getSql(), sqlMeta.getJdbcParamValues());
                // 数据转换插件
                String transform_plugin = config.getTransformPlugin();
                String transformPluginParams = config.getTransformPluginParams();
                if (!StringUtils.isEmpty(transform_plugin)) {
                    if (data instanceof List) {
                        List<JSONObject> nd = (List<JSONObject>) data;
                        data = PluginManager
                                .getTransformPlugin(transform_plugin)
                                .transform(nd, transformPluginParams);
                    }
                }
                result.add(data);
            }

            Object res = result;
            if (result.size() == 1) {
                res = result.get(0);
            }

            ResponseDto dto = ResponseDto.apiSuccess(res);

            // 缓存插件
            if (!StringUtils.isEmpty(config.getCachePlugin())) {
                PluginManager.getCachePlugin(config.getCachePlugin()).set(config, sqlParam, dto.getData());
            }
            return dto;
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseDto.fail(e.getMessage() == null ? e.toString() : e.getMessage());
        }
    }

    private boolean checkAuth(List<String> authGroups, String groupId) {
        for (String authGroup : authGroups) {
            if (authGroup.equals(groupId)) return true;
        }
        return false;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    public void destroy() {
        super.destroy();
    }

    @Override
    public void init() throws ServletException {
        super.init();
    }
}
